.com
Hosted by:
Unit testing expertise at your fingertips!
Home | Discuss | Lists

Test Hook

The book has now been published and the content of this chapter has likely changed substanstially.
Please see page 709 of xUnit Test Patterns for the latest information.
How do we design the system under test (SUT) so that we can replace its dependencies at run time?

We modify the SUT to behave differently during the test.

Sketch Test Hook embedded from Test Hook.gif

Almost every piece of code depends on some other classes, objects, modules or procedures. To unit test a piece of code properly, we would like to isolate it from its dependencies. This is hard to do if those dependencies are hard-coded within the code in the form of literal class names.

   public String getCurrentTimeAsHtmlFragment() {
      Calendar currentTime;
      try {
         currentTime = new DefaultTimeProvider().getTime();
      } catch (Exception e) {
         return e.getMessage();
      }
      // etc.}
Example HardCodedDependency embedded from java/com/clrstream/ex7/problem/TimeDisplay.java

Test Hook is a "method of last resort" for introducing test-specific behavior during automated testing.

How It Works

We modify the behavior of the SUT to support testing by putting a hook right into the SUT or into a depended-on component (DOC). This implies some kind of testing flag that can be checked in the appropriate place.

When To Use It

Sometimes it is appropriate to use this "pattern of last resort" when we cannot use either Dependency Injection (page X) or Dependency Lookup (page X). We use it because we have no other way to address the Untested Code (see Production Bugs on page X) caused by a Hard-Coded Dependency (see Hard to Test Code on page X).

Test Hook may be the only way to introduce Test Double (page X) behavior when we are programming in a procedural language that does not support objects, function pointers or any other form of dynamic binding.

Test Hooks can be used as a transitionary strategy to get legacy code under test. We can introduce testability using the Test Hooks and then use those Tests as Safety Net (see Goals of Test Automation on page X) while we refactor for even more testability. At some point we should be able to discard the initial round of tests that required the Test Hooks because we have enough "modern" tests to protect us.

Implementation Notes

The essence of Test Hook is to insert some code into the SUT that lets us test. Regardless of how we insert this code into the SUT, the code itself can either:

The flag that indicates testing is in progress can be a compile time constant which may cause the compiler to optimize out all the testing logic. In languages that support preprocessors or compiler macros, these can also be used to remove the Test Hook before going into production. The flag can also be read in from configuration data or stored in a global variable that can be set directly by the test.

Motivating Example

This is an example of a test which cannot be made to pass "as is":

   public void testDisplayCurrentTime_AtMidnight() {
      // fixture setup
      TimeDisplay sut = new TimeDisplay();
      // exercise sut
      String result = sut.getCurrentTimeAsHtmlFragment();
      // verify direct output
      String expectedTimeString = "<span class=\"tinyBoldText\">Midnight</span>";
      assertEquals( expectedTimeString, result);
   }
Example NondeterministicTest embedded from java/com/clrstream/ex7/test/TimeDisplayTest.java

This test almost always fails because it depends on the current time being returned to the SUT by a depended-on component. The values being returned by that component, the DefaultTimeProvider, cannot be controlled by the test. Therefore, this test will only pass when the system time is exactly midnight.

   public String getCurrentTimeAsHtmlFragment() {
      Calendar currentTime;
      try {
         currentTime = new DefaultTimeProvider().getTime();
      } catch (Exception e) {
         return e.getMessage();
      }
      // etc.}
Example HardCodedDependency embedded from java/com/clrstream/ex7/problem/TimeDisplay.java

Because the SUT is hard-coded to use a particular class to retrieve the time, there is no way to replace the depended-on component with a Test Double. That makes this test nondeterministic and pretty much useless. We need to find a way to get control of the indirect inputs of the SUT.

Refactoring Notes

We can introduce a Test Hook by creating a flag that can be checked in the SUT. We then wrap the production code with an if/then/else control structure and put the test-specific logic into the then clause.

Example: Test Hook in System Under Test

Here's the production code modifed to accomodate testing via a Test Hook:

   public String getCurrentTimeAsHtmlFragment() {
      Calendar theTime;
      try {
         if (TESTING) {
            theTime = new GregorianCalendar();
            theTime.set(Calendar.HOUR_OF_DAY, 0);
            theTime.set(Calendar.MINUTE, 0);}
         else {
            theTime = new DefaultTimeProvider().getTime();
         }        
      } catch (Exception e) {
         return e.getMessage();
      }
      // etc.
Example TestHookInSUT embedded from java/com/xunitpatterns/dft/lookup/HookedTimeDisplay.java

In this case we have implemented the testing flag as global constant that can be edited. This implies a separate build for versions of the system to be tested. This is somewhat safer than using a dynamic configuration parameter or member variable because many compilers will optimize this hook right out of the object code.

Example: Test Hook in Depended-on Component

We can also introduce a Test Hook putting the hook into a DOC rather than into the SUT.

public Calendar getTime() throws TimeProviderEx {
      Calendar theTime = new GregorianCalendar();
      if (TESTING) {  
         theTime.set(Calendar.HOUR_OF_DAY, 0);
         theTime.set(Calendar.MINUTE, 0);}
      else {
         // just return the calendar
      }        
      return theTime;
   };
Example TestHookInDOC embedded from java/com/xunitpatterns/dft/lookup/HookedTimeProvider.java

This is somewhat better because we are not modifying the SUT as we test it.



Page generated at Wed Feb 09 16:39:35 +1100 2011

Copyright © 2003-2008 Gerard Meszaros all rights reserved

All Categories
Introductory Narratives
Web Site Instructions
Code Refactorings
Database Patterns
DfT Patterns
External Patterns
Fixture Setup Patterns
Fixture Teardown Patterns
Front Matter
Glossary
Misc
References
Result Verification Patterns
Sidebars
Terminology
Test Double Patterns
Test Organization
Test Refactorings
Test Smells
Test Strategy
Tools
Value Patterns
XUnit Basics
xUnit Members
All "DfT Patterns"
Dependency Injection
Dependency Lookup
Humble Object
Test Hook